Completed
Push — master ( aae0d8...1c2097 )
by Jeff
03:07
created

Content.setPreloaded   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 20
rs 8.2222
1
/** global: updateScreenUrl */
2
3
/**
4
 * Screen class constructor
5
 * @param {string} updateScreenUrl global screen update checks url
6
 */
7
function Screen(updateScreenUrl) {
8
  this.fields = [];
9
  this.url = updateScreenUrl;
10
  this.lastChanges = null;
11
  this.endAt = null;
12
  this.nextUrl = null;
13
  this.stopping = false;
14
  this.cache = {};
15
}
16
17
/**
18
 * Ajax GET on updateScreenUrl to check lastChanges timestamp and reload if necessary
19
 */
20
Screen.prototype.checkUpdates = function() {
21
  var s = this;
22
  $.get(this.url, function(j) {
23
    if (j.success) {
24
      if (s.lastChanges == null) {
25
        s.lastChanges = j.data.lastChanges;
26
      } else if (s.lastChanges != j.data.lastChanges) {
27
        s.reload();
28
        s.nextUrl = null;
29
        return;
30
      }
31
32
      if (j.data.duration > 0) {
33
        // Setup next screen
34
        s.reload(j.data.duration * 1000);
35
        s.nextUrl = j.data.nextScreenUrl;
36
      }
37
    }
38
  });
39
}
40
41
/**
42
 * Start Screen reload procedure, checking for every field timeout
43
 */
44
Screen.prototype.reload = function(minDuration) {
45
  var endAt = Date.now() + (minDuration ? minDuration : 0);
46
  if (this.stopping && this.endAt < endAt) {
47
    return;
48
  }
49
50
  this.endAt = minDuration ? Date.now() + minDuration : 0;
51
  this.stopping = true;
52
  for (var i in this.fields) {
53
    if (!this.fields.hasOwnProperty(i)) {
54
      continue;
55
    }
56
    var f = this.fields[i];
57
    if (f.timeout && f.endAt > this.endAt) {
58
      this.endAt = f.endAt;
59
    }
60
  }
61
62
  if (this.endAt === 0) {
63
    this.doReload();
64
  }
65
}
66
67
/**
68
 * Actual Screen reload action
69
 */
70
Screen.prototype.doReload = function() {
71
  if (this.nextUrl) {
72
    window.location = this.nextUrl;
73
  } else {
74
    window.location.reload();
75
  }
76
}
77
78
/**
79
 * Check every field for content
80
 * @param  {Content} data 
81
 * @return {boolean} content is displayed
82
 */
83
Screen.prototype.displaysData = function(data) {
84
  return this.fields.filter(function(field) {
85
    return field.current && field.current.data == data;
86
  }).length > 0;
87
}
88
89
/**
90
 * Content class constructor
91
 * @param {array} c content attributes
92
 */
93
function Content(c) {
94
  this.id = c.id;
95
  this.data = c.data;
96
  this.duration = c.duration * 1000;
97
  this.type = c.type;
98
  this.src = null;
99
100
  if (this.shouldPreload()) {
101
    this.preload();
102
  }
103
}
104
105
/**
106
 * Check if content should be ajax preloaded
107
 * @return {boolean}
108
 */
109
Content.prototype.shouldPreload = function() {
110
  return this.canPreload() && !this.isPreloading() && !this.isPreloaded();
111
}
112
113
/**
114
 * Check if content has pre-loadable material
115
 * @return {boolean} 
116
 */
117
Content.prototype.canPreload = function() {
118
  return this.getResource() && this.type.search(/Video|Image|Agenda/) != -1;
119
}
120
121
/**
122
 * Extract url from contant data
123
 * @return {string} resource url
124
 */
125
Content.prototype.getResource = function() {
126
  if (this.src) {
127
    return this.src;
128
  }
129
  var srcMatch = this.data.match(/src="([^"]+)"/);
130
  if (!srcMatch) {
131
    return false;
132
  }
133
  var src = srcMatch[1];
134
  if (src.indexOf('/') === 0) {
135
    src = window.location.origin + src;
136
  }
137
  if (src.indexOf('http') !== 0) {
138
    return false;
139
  }
140
141
  this.src = src;
142
  return src;
143
}
144
145
/**
146
 * Check cache for preload status of content
147
 * @return {Boolean} 
148
 */
149
Content.prototype.isPreloaded = function() {
150
  if (!this.canPreload()) {
151
    return true;
152
  }
153
154
  var cache = screen.cache[this.getResource()]
155
  switch (cache) {
156
    case undefined: // unset
157
    case false: // preloading
158
      return false;
159
    case true: // preloaded without expire
160
      return true;
161
    default: // check expire
162
      return (new Date()).valueOf() < cache;
163
  }
164
}
165
166
/**
167
 * Set content cache status
168
 * @param {string} expires header
169
 */
170
Content.prototype.setPreloaded = function(expires) {
171
  switch (expires) {
172
    case null:
173
    case undefined:
174
      // No Expires header, don't bother with caching
175
    case true:
176
      // Force content cache
177
      screen.cache[this.getResource()] = true;
178
      break;
179
    case false:
180
      // Discard content cache and disallow display
181
      delete screen.cache[this.getResource()];
182
      break;
183
    default:
184
      var exp = new Date(expires).valueOf();
185
      var diff = exp - (new Date()).valueOf();
186
      // Don't check short Expires and add 5 sec to valid Expires
187
      screen.cache[this.getResource()] = diff < 10000 ? true : exp + 5000;
188
  }
189
}
190
191
/**
192
 * Check cache for in progress preloading
193
 * @return {Boolean}
194
 */
195
Content.prototype.isPreloading = function() {
196
  return screen.cache[this.getResource()] === false;
197
}
198
199
/**
200
 * Set content preloading status
201
 * @param {boolean} state is preloading
202
 */
203
Content.prototype.setPreloading = function(state) {
204
  if (state) {
205
    screen.cache[this.getResource()] = false;
206
  } else if (this.isPreloading()) {
207
    delete screen.cache[this.getResource()];
208
  }
209
}
210
211
/**
212
 * Ajax call to preload content
213
 * @return {[type]} [description]
214
 */
215
Content.prototype.preload = function() {
216
  var src = this.getResource();
217
  if (!src) {
218
    this.setPreloaded(true);
219
    return;
220
  }
221
  this.setPreloading(true);
222
223
  var c = this;
224
  $.ajax(src).done(function(data, textStatus, jqXHR) {
225
    c.setPreloaded(jqXHR.getResponseHeader('Expires'));
226
  }).fail(function() {
227
    c.setPreloaded(false); // Discard until next Content init
228
  });
229
}
230
231
/**
232
 * Field class constructor
233
 * @param {jQuery.Object} $f field object
234
 */
235
function Field($f) {
236
  this.$field = $f;
237
  this.id = $f.attr('data-id');
238
  this.url = $f.attr('data-url');
239
  this.types = $f.attr('data-types').split(' ');
240
  this.canUpdate = this.url != null;
241
  this.contents = [];
242
  this.previous = null;
243
  this.current = null;
244
  this.next = null;
245
  this.timeout = null;
246
  this.endAt = null;
247
}
248
249
/**
250
 * Retrieves contents from backend for this field
251
 */
252
Field.prototype.getContents = function() {
253
  if (!this.canUpdate) {
254
    return;
255
  }
256
257
  var f = this;
258
  $.get(this.url, function(j) {
259
    if (j.success) {
260
      f.contents = j.next.map(function(c) {
261
        return new Content(c);
262
      });
263
      if (!f.timeout && f.contents.length) {
264
        f.pickNext();
265
      }
266
    } else {
267
      f.setError(j.message || 'Error');
268
    }
269
  });
270
}
271
272
/**
273
 * Display error in field text
274
 */
275
Field.prototype.setError = function(err) {
276
  this.$field.text(err);
277
}
278
279
/**
280
 * Randomize order
281
 */
282
Field.prototype.randomizeSortContents = function() {
283
  this.contents = this.contents.sort(function(a, b) {
0 ignored issues
show
Unused Code introduced by
The parameter b is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter a is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
284
    return Math.random() - 0.5;
285
  });
286
}
287
288
/**
289
 * Loop through field contents to pick next displayable content
290
 */
291
Field.prototype.pickNext = function() {
292
  if (screen.stopping && screen.endAt < Date.now()) { // Stoping screen
293
    screen.doReload();
294
    return;
295
  }
296
297
  this.previous = this.current;
298
  this.current = null;
299
  var pData = this.previous && this.previous.data;
300
  // Avoid repeat & other field same content
301
  this.randomizeSortContents();
302
  for (var i = 0; i < this.contents.length; i++) {
303
    var c = this.contents[i];
304
    // Skip too long or not preloaded content 
305
    if ((screen.endAt != null && c.duration + Date.now() > screen.endAt) || !c.isPreloaded()) {
306
      continue;
307
    }
308
309
    if (c.data == pData) {
310
      // Will repeat, avoid if enough content
311
      if (this.contents.length < 2) {
312
        this.next = c;
313
        break;
314
      }
315
      continue;
316
    }
317
318
    if (screen.displaysData(c.data)) {
319
      // Same content already displayed on other field, avoid if enough content
320
      if (this.contents.length < 3) {
321
        this.next = c;
322
        break;
323
      }
324
      continue;
325
    }
326
327
    this.next = c;
328
  }
329
330
  this.display();
331
}
332
333
/**
334
 * Display next content in field html
335
 */
336
Field.prototype.display = function() {
337
  var f = this;
338
  if (this.next && this.next.duration > 0) {
339
    this.current = this.next
340
    this.next = null;
341
    this.$field.html(this.current.data);
342
    this.$field.show();
343
    if (this.$field.text() != '') {
344
      this.$field.textfill({
345
        maxFontPixels: 0,
346
      });
347
    }
348
    if (this.timeout) {
349
      clearTimeout(this.timeout);
350
    }
351
    this.timeout = setTimeout(function() {
352
      f.pickNext();
353
    }, this.current.duration);
354
    this.endAt = this.current.duration + Date.now()
355
  } else {
356
    this.timeout = setTimeout(function() {
357
      f.pickNext();
358
    }, 2000);
359
  }
360
}
361
362
/**
363
 * jQuery.load event
364
 * Initialize Screen and Fields
365
 * Setup updates interval timeouts
366
 */
367
var screen = null;
368
369
function onLoad() {
370
  screen = new Screen(updateScreenUrl);
371
  // Init
372
  $('.field').each(function() {
373
    var f = new Field($(this));
374
    f.getContents();
375
    screen.fields.push(f);
376
  });
377
378
  // Setup content updates loop
379
  setInterval(function() {
380
    for (var f in screen.fields) {
381
      if (screen.fields.hasOwnProperty(f)) {
382
        screen.fields[f].getContents();
383
      }
384
    }
385
    screen.checkUpdates();
386
  }, 60000);
387
  screen.checkUpdates();
388
}
389
390
// Run
391
$(onLoad);
392